package fem2.gui;

import fem2.Model;
import fem2.gui.pm.HofemPipelineFactory;
import inf.common.util.awt.AwtUtil;
import inf.swing.IntTextField;
import inf.text.ArrayFormat;
import inf.vtk.pm.PipelineCollection;

import java.awt.BorderLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.AbstractAction;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * This class contains the whole toolbar to animate the model
 * 
 * @author christianm
 */
public class AnimateToolbar extends JToolBar implements PropertyChangeListener {

	/**
	 * ToolbarAction to jump to the beginning of the animation.
	 */
	class AnimationJumpToBeginningAction extends ToolbarAction {

		private static final long serialVersionUID = 1L;

		AnimationJumpToBeginningAction() {
			super("jump to beginning", "control_start_blue", "png", false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			jumpToBeginning();
		}
	}

	/**
	 * ToolbarAction to jump to the ned of the animation.
	 */
	class AnimationJumpToEndAction extends ToolbarAction {

		private static final long serialVersionUID = 1L;

		AnimationJumpToEndAction() {
			super("jump to end", "control_end_blue", "png", false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			jumpToEnd();
		}
	}

	/**
	 * ToolbarAction to go one step backward in the animation.
	 */
	class AnimationStepBackwardAction extends ToolbarAction {

		private static final long serialVersionUID = 1L;

		AnimationStepBackwardAction() {
			super("step backward", "control_rewind_blue", "png", false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			stepBackward();
		}
	}

	/**
	 * ToolbarAction to go one step forward in the animation.
	 */
	class AnimationStepForwardAction extends ToolbarAction {

		private static final long serialVersionUID = 1L;

		AnimationStepForwardAction() {
			super("step forward", "control_fastforward_blue", "png", false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			stepForward();
		}
	}

	/**
	 * ToolbarAction to pause the animation.
	 */
	class PauseAnimationAction extends ToolbarAction {

		private static final long serialVersionUID = 1L;

		PauseAnimationAction() {
			super("pause animation", "control_pause_blue", "png", false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			pause();
		}
	}

	/**
	 * ToolbarAction to start the animation.
	 */
	class StartAnimationAction extends ToolbarAction {

		private static final long serialVersionUID = 1L;

		StartAnimationAction() {
			super("start animation", "control_play_blue", "png", false);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			animate();
		}
	}

	/**
	 * The abstract base class for all actions used to control the animation.
	 */
	protected abstract class ToolbarAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		boolean state_;

		public ToolbarAction(String name, String imageName, boolean state) {
			super(name, AwtUtil.getIcon("icons/" + imageName + ".gif"));
			state_ = state;
		}

		public ToolbarAction(String name, String imageName, String extension, boolean state) {
			super(name, new ImageIcon(Toolkit.getDefaultToolkit().createImage(
					AnimateToolbar.class.getClassLoader().getResource(
							"icons/" + imageName + "." + extension))));
			state_ = state;
		}
	}

	private static final long serialVersionUID = 1L;

	// gui variables
	private IntTextField loadStepITF_ = new IntTextField(1, 3);
	private IntTextField substepITF_ = new IntTextField(1, 3);
	private JLabel timeTF_ = new JLabel("9999999999");
	private boolean paused_ = true;

	// actions
	private StartAnimationAction startAction_ = new StartAnimationAction();
	private PauseAnimationAction pauseAction_ = new PauseAnimationAction();
	private AnimationStepBackwardAction stepBackwardAction_ = new AnimationStepBackwardAction();
	private AnimationStepForwardAction stepForwardAction_ = new AnimationStepForwardAction();
	private AnimationJumpToBeginningAction jumpToBeginningAction_ = new AnimationJumpToBeginningAction();
	private AnimationJumpToEndAction jumpToEndAction_ = new AnimationJumpToEndAction();

	// vtk variables
	private LoadStepMgrAdapter lsmAdapter_;
	private PipelineCollection pipelineCollection_;
	private ModelRenderer modelRenderer_ = new ModelRenderer() {

		@Override
		public ModelHolder getModelHolder() {
			return (ModelHolder) pipelineCollection_.getModel();
		}

		@Override
		public void renderModel() {
			if (!SwingUtilities.isEventDispatchThread()) {
				try {
					SwingUtilities.invokeAndWait(new Runnable() {

						@Override
						public void run() {
							pipelineCollection_.render();
						}
					});
				} catch (Exception e) {
					e.printStackTrace();
				}
			} else {
				pipelineCollection_.render();
			}
		}
	};

	private Model lastModel_;

	/**
	 * Creates a new AnimateToolbar for the given PipelineCollection.
	 * 
	 * @param pc
	 *            The PipelineCollection.
	 */
	public AnimateToolbar(PipelineCollection pc) {
		// kind of a hack. needed to receive model change events.
		pc.getPart(HofemPipelineFactory.PL_LINE_PLOT).addPropertyChangeListener(this);
		pipelineCollection_ = pc;
		lsmAdapter_ = new LoadStepMgrAdapter(modelRenderer_);

		// listeners
		ActionListener al = new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				apply();
			}
		};
		ChangeListener cl = new ChangeListener() {

			@Override
			public void stateChanged(ChangeEvent e) {
				apply();
			}
		};
		KeyListener kl = new KeyListener() {

			@Override
			public void keyPressed(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_UP) {
					stepForward();
				} else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
					stepBackward();
				}
			}

			@Override
			public void keyReleased(KeyEvent e) {
			}

			@Override
			public void keyTyped(KeyEvent e) {
			}

		};
		MouseWheelListener mwl = new MouseWheelListener() {

			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {
				if (e.getWheelRotation() < 0) {
					stepForward();
				} else {
					stepBackward();
				}
			}
		};

		// text fields
		loadStepITF_.addActionListener(al);
		loadStepITF_.addChangeListener(cl);
		substepITF_.addActionListener(al);
		substepITF_.addChangeListener(cl);
		for (KeyListener kll : substepITF_.getKeyListeners()) {
			substepITF_.removeKeyListener(kll);
		}
		substepITF_.addKeyListener(kl);
		substepITF_.addMouseWheelListener(mwl);

		// disable components
		startAction_.setEnabled(false);
		pauseAction_.setEnabled(false);
		stepBackwardAction_.setEnabled(false);
		stepForwardAction_.setEnabled(false);
		jumpToBeginningAction_.setEnabled(false);
		jumpToEndAction_.setEnabled(false);

		// add components and actions
		Box p2 = new Box(BoxLayout.X_AXIS);
		// p2.add(new JLabel(" Load step: "));
		// p2.add(loadStepITF_);
		p2.add(new JLabel("Timestep: "));
		p2.add(substepITF_);
		p2.add(new JLabel(" Time: "));
		p2.add(timeTF_);
		// p2.setMaximumSize(p2.getPreferredSize());
		add(p2);
		add(jumpToBeginningAction_);
		add(stepBackwardAction_);
		add(startAction_);
		add(pauseAction_);
		add(stepForwardAction_);
		add(jumpToEndAction_);
		add(new JPanel(new BorderLayout()));
	}

	/**
	 * Starts the animation.
	 */
	private void animate() {
		paused_ = false;
		Thread animation = new Thread() {
			@Override
			public void run() {
				int substep = substepITF_.getIntValue();
				int ssc = lsmAdapter_.getSubstepCount(0);
				for (int j = substep - 1; !paused_ && j <= ssc; j++) {
					substepITF_.setIntValue(j + 1);
					apply();
				}
				substep = 1;
				paused_ = true;
				update();
			}
		};
		animation.start();
	}

	private class ApplyRunnable implements Runnable {

		@Override
		public void run() {
			int loadStep = loadStepITF_.getIntValue() - 1;
			int substep = substepITF_.getIntValue() - 1;
			setValues(loadStep, substep);
		}
	}

	private void apply() {
		ApplyRunnable ar = new ApplyRunnable();

		if (SwingUtilities.isEventDispatchThread()) {
			ar.run();
		} else {
			try {
				SwingUtilities.invokeAndWait(ar);
			} catch (Exception e) {
				throw new Error(e);
			}
		}
	}

	private boolean atBeginning() {
		return lsmAdapter_.getCurrentSubstep() == 0 && lsmAdapter_.getCurrentLoadStep() == 0;
	}

	private boolean atEnd() {
		int lsc = lsmAdapter_.getLoadStepCount();
		int ssc = lsmAdapter_.getSubstepCount(lsc - 1);
		int cls = lsmAdapter_.getCurrentLoadStep();
		int css = lsmAdapter_.getCurrentSubstep();
		return cls == lsc - 1 && css == ssc - 1;
	}

	private static int clip(int value, int upper) {
		if (value < 0) {
			return 0;
		}
		if (value >= upper) {
			return upper - 1;
		}
		return value;
	}

	private void jumpToBeginning() {
		setValues(0, 0);
	}

	private void jumpToEnd() {
		setValues(Integer.MAX_VALUE, Integer.MAX_VALUE);
	}

	private void pause() {
		paused_ = true;
	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		update();
	}

	private void setTime(double time) {
		timeTF_.setText(ArrayFormat.format(time));
		// if (time == NSSolver.UNCONVERGED_T) {
		// timeTF_.setForeground(Color.RED);
		// } else {
		// timeTF_.setForeground(Color.DARK_GRAY);
		// }
	}

	private void setValues(int loadStep, int substep) {
		if (loadStep < 0) {
			loadStep = 0;
		} else if (loadStep >= lsmAdapter_.getLoadStepCount()) {
			loadStep = lsmAdapter_.getLoadStepCount() - 1;
		} else if (substep == -1 && loadStep > 0) {
			loadStep--;
			substep = Integer.MAX_VALUE;
		} else if (substep >= lsmAdapter_.getSubstepCount(loadStep)
				&& loadStep < lsmAdapter_.getLoadStepCount() - 1) {
			loadStep++;
			substep = 0;
		}
		substep = clip(substep, lsmAdapter_.getSubstepCount(loadStep));
		setTime(lsmAdapter_.getTime(loadStep, substep));
		lsmAdapter_.restore(loadStep, substep);
		update();
	}

	/**
	 * Goes one step backward in the animation.
	 */
	private void stepBackward() {
		int cls = lsmAdapter_.getCurrentLoadStep();
		int css = lsmAdapter_.getCurrentSubstep();
		setValues(cls, css - 1);
	}

	/**
	 * Goes one step forward in the animation.
	 */
	private void stepForward() {
		int cls = lsmAdapter_.getCurrentLoadStep();
		int css = lsmAdapter_.getCurrentSubstep();
		setValues(cls, css + 1);
	}

	private void update() {
		if (lsmAdapter_.getLoadStepCount() > 0) {
			int cls = lsmAdapter_.getCurrentLoadStep();
			int css = lsmAdapter_.getCurrentSubstep();
			int lsc = lsmAdapter_.getLoadStepCount();
			int ssc = lsmAdapter_.getSubstepCount(cls);
			boolean haveSteps = (lsc > 0 && lsmAdapter_.getSubstepCount(0) > 0);

			jumpToBeginningAction_.setEnabled(haveSteps && paused_ && !atBeginning());
			stepBackwardAction_.setEnabled(haveSteps && paused_ && !atBeginning());
			startAction_.setEnabled(haveSteps && paused_ && !atEnd());
			pauseAction_.setEnabled(haveSteps && !paused_);
			stepForwardAction_.setEnabled(haveSteps && paused_ && !atEnd());
			jumpToEndAction_.setEnabled(haveSteps && paused_ && !atEnd());

			// update text fields
			loadStepITF_.setRange(1, lsc);
			substepITF_.setRange(1, ssc);
			loadStepITF_.setIntValue(cls + 1);
			substepITF_.setIntValue(css + 1);

			setTimestep();
		}
	}

	public void setTimestep() {
		Model m = modelRenderer_.getModelHolder().getModel();

		if (m != lastModel_) {
			lastModel_ = m;
			jumpToEnd();
		}
	}
}
